package net.towee.server.bittorrent.bee;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class BEEncoder {
	private ByteArrayOutputStream stream = new ByteArrayOutputStream();
	private static final String UTF8 = "utf-8";

	public byte[] encode(BEValue value) {
		try {
			innerEncode(value);
			return stream.toByteArray();
		} catch (IOException e) {
			throw new InvalidBEEncodingException("Error encoding value", e);
		}
	}

	public void innerEncode(BEValue value) throws IOException {
		Object innerValue = value.getValue();

		if (innerValue instanceof byte[]) {
			encodeByteArray((byte[]) innerValue);
		} else if (innerValue instanceof Number) {
			encodeNumber((Number) innerValue);
		} else if (innerValue instanceof List) {
			encodeList((List<BEValue>) innerValue);
		} else if (innerValue instanceof Map) {
			encodeMap((Map<String, BEValue>) innerValue);
		} else {
			throw new InvalidBEEncodingException("Unnable to encode, wrong type: "
					+ innerValue.getClass().toString());
		}

	}

	private void encodeByteArray(byte[] innerValue) throws IOException {
		stream.write((Integer.toString(innerValue.length) + ":").getBytes(UTF8));
		stream.write(innerValue);
	}

	private void encodeNumber(Number innerValue) throws IOException {
		stream.write(("i" + innerValue.toString() + "e").getBytes(UTF8));
	}

	private void encodeList(List<BEValue> innerValue) throws IOException {
		stream.write("l".getBytes(UTF8));
		for (BEValue value : innerValue) {
			innerEncode(value);
		}
		stream.write("e".getBytes(UTF8));
	}

	private void encodeMap(Map<String, BEValue> innerValue) throws IOException {
		stream.write("d".getBytes(UTF8));
		
		// Write announce url
		if(innerValue.containsKey("announce")) {
			innerEncode(new BEValue("announce"));
			innerEncode(innerValue.get("announce"));
		}
		
		// Then announce-list
		if(innerValue.containsKey("announce-list")) {
			innerEncode(new BEValue("announce-list"));
			innerEncode(innerValue.get("announce-list"));
		}
		
		// Only then other
		for (Entry<String, BEValue> entry : innerValue.entrySet()) {
			innerEncode(new BEValue(entry.getKey()));
			innerEncode(entry.getValue());
		}
		
		stream.write("e".getBytes(UTF8));
	}

}